package uz.rt.dicom.data.element;

import uz.rt.dicom.ControlCharacters;
import uz.rt.dicom.Padding;
import uz.rt.dicom.ValueRepresentation;
import uz.rt.dicom.data.ComparableList;
import uz.rt.dicom.data.StringElement;
import uz.rt.dicom.io.DicomIOException;
import uz.rt.dicom.io.DicomInputStream;
import uz.rt.dicom.tag.DicomTag;

import java.io.IOException;

/**
 */
public class PersonName extends StringElement implements Comparable<PersonName>
{
// ------------------------------ FIELDS ------------------------------

    /** An alphabetic representation of a name */
    private NameGroup[] alphabetic = null;
    /** An ideographic representation of a name */
    private NameGroup[] ideographic = null;
    /** An phonetic representation of a name */
    private NameGroup[] phonetic = null;

// --------------------------- CONSTRUCTORS ---------------------------

    public PersonName(DicomTag tag)
    {
        super(tag);
        setMaxLength(64); //maximum 64 char per component group???
        setPadding(Padding.TRAILING_SPACE);
    }

// ------------------------ INTERFACE METHODS ------------------------


// --------------------- Interface Comparable ---------------------

    /**
     * The function compares only the following parameters in order:
     * <ul>
     * <li>Dicom tag</li>
     * <li>Value multiplicity</li>
     * <li>Comparable list of data fields</li>
     * </ul>
     *
     * @param o PersonName
     * @return A negative integer, zero, or a positive integer as this object is less than, equal to, or greater than
     *         the specified object.
     */
    public int compareTo(PersonName o)
    {
        if (!tag.equals(o.getTag())) return tag.compareTo(o.getTag());
        if (!vm.equals(o.getVm())) return vm.compareTo(o.getVm());
        return value.compareTo(o.getValue());
    }

// --------------------- Interface IDataElement ---------------------

    @Override
    public byte[] getVR()
    {
        return ValueRepresentation.PN;
    }

    @Override
    public ValueRepresentation getValueRepresentation()
    {
        return ValueRepresentation.PersonName;
    }

    public void read(DicomInputStream dicomInputStream, int length) throws IOException
    {
        String nameGroupDelimiter = "=";
        String nameDelimiter = "\\^";
        int maxNamesPerGroup = 5;
        value = null;
        value = new ComparableList<String>();
        String s = dicomInputStream.readString(length, getCharacterSet()).trim();
        String[] groups = splitValues(s);
        if (groups.length < vm.getMin() || groups.length > vm.getMax())
        {
            value = null;
            throw new DicomIOException(
                    "Value multiplicity violation: group length = " + groups.length +
                            ", limits are [" + vm.toString() + "]");
        }
        alphabetic = new NameGroup[groups.length];
        ideographic = new NameGroup[groups.length];
        phonetic = new NameGroup[groups.length];
        int index = 0;
        for (String group : groups)
        {
            char c;
            for (int i = 0; i < group.length(); i++)
            {
                c = group.charAt(i);

                if (c == '\\' || c == ControlCharacters.CR || c == ControlCharacters.FF || c == ControlCharacters.LF)
                {
                    value = null;
                    alphabetic = null;
                    ideographic = null;
                    phonetic = null;
                    throw new DicomIOException(
                            "Backslash characters or control characters CR, FF and LF " +
                                    "are not allowed in an LongString object.");
                }
            }
            String[] nameGroups = group.split(nameGroupDelimiter);
            if (nameGroups[0].isEmpty()) alphabetic[index] = null;
            else
            {
                String[] tmp = nameGroups[0].split(nameDelimiter);
                String[] names = new String[maxNamesPerGroup];
                System.arraycopy(tmp, 0, names, 0, tmp.length);
                for (int j = tmp.length; j < names.length; j++)
                    names[j] = "";
                alphabetic[index] = null;
                alphabetic[index] = new NameGroup(names[0], names[1], names[2], names[3], names[4]);
            }
            if (nameGroups.length >= 2)
            {
                if (nameGroups[1].isEmpty()) ideographic[index] = null;
                else
                {
                    String[] tmp = nameGroups[1].split(nameDelimiter);
                    String[] names = new String[maxNamesPerGroup];
                    System.arraycopy(tmp, 0, names, 0, tmp.length);
                    for (int j = tmp.length; j < names.length; j++)
                        names[j] = "";
                    ideographic[index] = null;
                    ideographic[index] = new NameGroup(names[0], names[1], names[2], names[3], names[4]);
                }
            }
            if (nameGroups.length == 3)
            {
                if (nameGroups[2].isEmpty()) phonetic[index] = null;
                else
                {
                    String[] tmp = nameGroups[2].split(nameDelimiter);
                    String[] names = new String[maxNamesPerGroup];
                    System.arraycopy(tmp, 0, names, 0, tmp.length);
                    for (int j = tmp.length; j < names.length; j++)
                        names[j] = "";
                    phonetic[index] = null;
                    phonetic[index] = new NameGroup(names[0], names[1], names[2], names[3], names[4]);
                }
            }
            index++;
        }
    }

// -------------------------- OTHER METHODS --------------------------

    /**
     * Get the alphabetic name group
     *
     * @param index Index of the value that needs to be accessed. Value should be with the constraints of the value
     *              multiplicity.
     * @return Alphabetic name group
     */
    public NameGroup getAlphabetic(int index)
    {
        return alphabetic[index];
    }

    /**
     * Get the ideographic name group
     *
     * @param index Index of the value that needs to be accessed. Value should be with the constraints of the value
     *              multiplicity.
     * @return Ideographic name group
     */
    public NameGroup getIdeographic(int index)
    {
        return ideographic[index];
    }

    /**
     * Get the phonetic name group
     *
     * @param index Index of the value that needs to be accessed. Value should be with the constraints of the value
     *              multiplicity.
     * @return Phonetic name group
     */
    public NameGroup getPhonetic(int index)
    {
        return phonetic[index];
    }

    /**
     * Set the alphabetic name group
     *
     * @param index      Index of the value that needs to be accessed. Value should be with the constraints of the
     *                   value
     *                   multiplicity.
     * @param alphabetic Alphabetic name group
     */
    public void setAlphabetic(int index, NameGroup alphabetic)
    {
        this.alphabetic[index] = alphabetic;
    }

    /**
     * Set the ideographic name group
     *
     * @param index       Index of the value that needs to be accessed. Value should be with the constraints of the
     *                    value
     *                    multiplicity.
     * @param ideographic Ideographic name group
     */
    public void setIdeographic(int index, NameGroup ideographic)
    {
        this.ideographic[index] = ideographic;
    }

    /**
     * Set the phonetic name group
     *
     * @param index    Index of the value that needs to be accessed. Value should be with the constraints of the value
     *                 multiplicity.
     * @param phonetic Phonetic name group
     */
    public void setPhonetic(int index, NameGroup phonetic)
    {
        this.phonetic[index] = phonetic;
    }

// -------------------------- INNER CLASSES --------------------------

    /**
     * NameGroup is a subclass of PersonName since within on PersonName instance multiple name groups can exist.
     * Every name group represents a different representation in which the data is stored. The dicom standard defines 3
     * different representations:
     * <ul>
     * <li>an alphabetic representation</li>
     * <li>an ideographic representation</li>
     * <li>a phonetic representation</li>
     * </ul>
     */
    public class NameGroup implements Comparable<NameGroup>
    {
        /**
         * Get the last name of the person.
         * If not set the value is Null.
         *
         * @return Last name of the person.
         */
        public String getLastName()
        {
            return lastName;
        }

        /**
         * Set the last name of the person.
         *
         * @param lastName Last name of the person.
         */
        public void setLastName(String lastName)
        {
            this.lastName = lastName;
        }

        /**
         * Get the first name of the person.
         * If not set the value is Null.
         *
         * @return First name of the person.
         */
        public String getFirstName()
        {
            return firstName;
        }

        /**
         * Set the first name of the person.
         *
         * @param firstName First name of the person.
         */
        public void setFirstName(String firstName)
        {
            this.firstName = firstName;
        }

        /**
         * Get the middle name of the person.
         * If not set the value is Null.
         *
         * @return Middle name of the person.
         */
        public String getMiddleName()
        {
            return middleName;
        }

        /**
         * Set the middle name of the person.
         *
         * @param middleName Middle name of the person.
         */
        public void setMiddleName(String middleName)
        {
            this.middleName = middleName;
        }

        /**
         * Get the prefix of the name of the person.
         *
         * @return Prefix of the name of the person.
         */
        public String getPrefix()
        {
            return prefix;
        }

        /**
         * Set the prefix of the name of the person.
         *
         * @param prefix Prefix of the name of the person.
         */
        public void setPrefix(String prefix)
        {
            this.prefix = prefix;
        }

        /**
         * Get the suffix of the name of the person.
         *
         * @return Suffix of the name of the person.
         */
        public String getSuffix()
        {
            return suffix;
        }

        /**
         * Set the suffix of the name of the person.
         *
         * @param suffix Suffix of the name of the person.
         */
        public void setSuffix(String suffix)
        {
            this.suffix = suffix;
        }

        /** Last name of the person */
        private String lastName;
        /** First name of the person */
        private String firstName;
        /** Middle name of the person */
        private String middleName;
        /** Prefix to the name of the person */
        private String prefix;
        /** Suffix of the name of the person */
        private String suffix;

        /**
         * Creates a name group and set the last and first name of the person.
         *
         * @param lastName  Last name
         * @param firstName First name
         */
        public NameGroup(String lastName, String firstName)
        {
            this.lastName = lastName;
            this.firstName = firstName;
            this.middleName = "";
            this.prefix = "";
            this.suffix = "";
        }

        /**
         * Creates a name group and set the last and first name of the person.
         *
         * @param lastName   Last name
         * @param firstName  First name
         * @param middleName Middle name
         * @param prefix     Prefix to a name
         * @param suffix     Suffix of a name
         */
        public NameGroup(String lastName, String firstName, String middleName, String prefix, String suffix)
        {
            this.lastName = lastName;
            this.firstName = firstName;
            this.middleName = middleName;
            this.prefix = prefix;
            this.suffix = suffix;
        }

        @Override
        public boolean equals(Object o)
        {
            if (this == o) return true;
            if (!(o instanceof NameGroup)) return false;

            NameGroup nameGroup = (NameGroup) o;

            return !(firstName != null ? !firstName
                    .equals(nameGroup.firstName) : nameGroup.firstName != null) && !(lastName != null ? !lastName
                    .equals(nameGroup.lastName) : nameGroup.lastName != null) && !(middleName != null ? !middleName
                    .equals(nameGroup.middleName) : nameGroup.middleName != null) && !(prefix != null ? !prefix
                    .equals(nameGroup.prefix) : nameGroup.prefix != null) && !(suffix != null ? !suffix
                    .equals(nameGroup.suffix) : nameGroup.suffix != null);
        }

        @Override
        public int hashCode()
        {
            int result = lastName != null ? lastName.hashCode() : 0;
            result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
            result = 31 * result + (middleName != null ? middleName.hashCode() : 0);
            result = 31 * result + (prefix != null ? prefix.hashCode() : 0);
            result = 31 * result + (suffix != null ? suffix.hashCode() : 0);
            return result;
        }

        public int compareTo(NameGroup o)
        {
            if (!lastName.equals(o.lastName)) return lastName.compareTo(o.lastName);
            if (!firstName.equals(o.firstName)) return firstName.compareTo(firstName);
            if (!middleName.equals(o.middleName)) return middleName.compareTo(middleName);
            if (!prefix.equals(o.prefix)) return prefix.compareTo(o.prefix);
            if (!suffix.equals(o.suffix)) return suffix.compareTo(o.suffix);
            return 0;
        }

        @Override
        public String toString()
        {
            StringBuilder sb = new StringBuilder();
            sb.append(prefix).append(" ");
            sb.append(firstName).append(" ");
            sb.append(middleName).append(" ");
            sb.append(lastName).append(" ");
            sb.append(suffix);
            return sb.toString().trim();
        }
    }
}
